/*
 *  Copyright 2005 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.common.parsers.html;


import com.blandware.atleap.common.parsers.exception.PlainTextExtractorException;
import com.blandware.atleap.common.parsers.SpecificPlainTextExtractor;
import com.blandware.atleap.common.Constants;

import javax.swing.text.ChangedCharSetException;
import java.io.*;
import java.util.StringTokenizer;
import java.util.List;

/**
 * <p>
 * An extractor that extracts a plain text from HTML. That extractor can accept
 * not only a full HTML-documents with &lt;html&gt;, &lt;body&gt; and other
 * tags that are needed, but it can accept just a piece of HTML text.
 * </p>
 * <p>
 * Additionally, this class can extract inline resources (&lt;IMG&gt; tag
 * <b>src</b> attribute, &lt;LINK&gt; tag <b>src</b> and <b>href</b> attributes
 * and <b>background</b> attribute for all tags) with
 * <code>extractInlineRecources()</code> and references (<b>src</b>, <b>href</b>
 * and <b>background</b> attributes) with <code>extractAllRefs()</code>.
 * </p>
 *
 * @see SpecificPlainTextExtractor
 * @author Roman Puchkovskiy <a href="mailto:roman.puchkovskiy@blandware.com">
 * &lt;roman.puchkovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.6 $ $Date: 2006/03/26 11:47:32 $
 */
public class HTMLPlainTextExtractor implements SpecificPlainTextExtractor {

    protected String usedEncoding = null;

    /**
     * Constructs new HTMLPlainTextExtractor instance
     */
    public HTMLPlainTextExtractor() {
    }

    /**
     * Extracts a plain text from an HTML.
     *
     * @param input the input stream that supplies an HTML for extraction
     * @param output the writer that will accept the extracted text
     * @param encoding If encoding is specified and extractor didn't find
     * encoding info in HTML, given encoding will be used to extract text.
     * If it's <code>null</code> and extractor didn't find extractor info in
     * document, default encoding (<code>Constants.DEFAULT_ENCODING</code>) will
     * be used. If extractor found such info, this parameter is ignored.
     * @throws PlainTextExtractorException throwed on exception raised during
     * extracting
     */
    public void extract(InputStream input, Writer output, String encoding)
            throws PlainTextExtractorException {
        HTMLParser parser = null;
        Reader reader = null;

        try {
            if (encoding == null || encoding.trim().length() == 0) {
                encoding = Constants.DEFAULT_ENCODING;
            }
            usedEncoding = encoding;

            RewindableInputStreamWrapper rewindableInputStreamWrapper
                    = new RewindableInputStreamWrapper(input);
            reader = new InputStreamReader(rewindableInputStreamWrapper, encoding);
            parser = new PlainTextParser(reader, output, true,
                                         rewindableInputStreamWrapper);
            try {
                // If no exception is thrown here, then the document has been parsed
                // using given (or default) encoding
                parser.parse();
            } catch (ChangedCharSetException e) {
                // There's some META in docuemnt that specifies the encoding
                String inputEncodingSpec = e.getCharSetSpec();
                String inputEncoding = (e.keyEqualsCharSet())
                                       ? inputEncodingSpec
                                       : getEncodingFromSpec(inputEncodingSpec);
                usedEncoding = inputEncoding;
                reader = new BufferedReader(new InputStreamReader(new PrefilledInputStream(rewindableInputStreamWrapper),
                                                                  inputEncoding));
                parser = new PlainTextParser(reader, output, false, null);
                parser.parse();
            }
        } catch (Exception e) {
            throw new PlainTextExtractorException(e);
        }
    }

    /**
     * Gets encoding from content-type spec like 'text/html; charset=koi8-r'.
     * @param spec the content-type specification
     * @return the encoding
     */
    private String getEncodingFromSpec(String spec) {
        String inputEncoding;
        StringTokenizer tokenizer = new StringTokenizer(spec, " \t;=");
        int tokenCount = tokenizer.countTokens();
        String[] tokens = new String[tokenCount];
        for (int i = 0; i < tokenCount; i++) {
            tokens[i] = tokenizer.nextToken();
        }
        if (tokenCount >= 3 && "charset".equalsIgnoreCase(tokens[1])) {
            inputEncoding = tokens[2];
        } else {
            inputEncoding = Constants.DEFAULT_ENCODING;
        }
        return inputEncoding;
    }

    /**
     * Extracts inline resources. Those are &lt;IMG&gt; tag
     * <b>src</b> attribute, &lt;LINK&gt; tag <b>src</b> and <b>href</b> attributes
     * and <b>background</b> attribute for all tags.
     *
     * @param input the input stream that supplies an HTML for extraction
     * @param encoding the encoding of the input stream
     * @return list of inline resources
     * @throws PlainTextExtractorException
     */
    public List extractInlineResources(InputStream input, String encoding)
            throws PlainTextExtractorException {
        try {
            Reader reader = new InputStreamReader(input, encoding);
            InlineResourcesParser parser = new InlineResourcesParser(reader);
            parser.parse();
            return parser.getExtractedResources();
        } catch (IOException e) {
            throw new PlainTextExtractorException(e);
        } catch (ParseException e) {
            throw new PlainTextExtractorException(e);
        }
    }

    /**
     * Extracts all references. Those are <b>src</b>, <b>href</b>
     * and <b>background</b> attributes.
     *
     * @param input the input stream that supplies an HTML for extraction
     * @param encoding the encoding of the input stream
     * @return list of all references
     * @throws PlainTextExtractorException
     */
    public List extractAllRefs(InputStream input, String encoding)
            throws PlainTextExtractorException {
        try {
            Reader reader = new InputStreamReader(input, encoding);
            RefsParser parser = new RefsParser(reader);
            parser.parse();
            return parser.getExtractedRefs();
        } catch (IOException e) {
            throw new PlainTextExtractorException(e);
        } catch (ParseException e) {
            throw new PlainTextExtractorException(e);
        }
    }

    /**
     * @see com.blandware.atleap.common.parsers.SpecificPlainTextExtractor#getUsedEncoding()
     */
    public String getUsedEncoding() {
        return usedEncoding;
    }
}